package com.ruoyi.system.service.impl;


import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.uuid.UUID;
import com.ruoyi.system.domain.TbAppCatalog;
import com.ruoyi.system.domain.TbAppVersion;
import com.ruoyi.system.domain.info.AppVersionInfo;
import com.ruoyi.system.domain.info.DataPackageInfo;
import com.ruoyi.system.domain.info.PackageInfo;

import com.ruoyi.system.domain.vo.AppVersionUploadResult;
import com.ruoyi.system.domain.vo.PatchPackageInfo;
import com.ruoyi.system.domain.vo.PatchResult;
import com.ruoyi.system.mapper.TbAppCatalogMapper;
import com.ruoyi.system.mapper.TbAppVersionMapper;
import com.ruoyi.system.service.IAppPackageService;
import com.ruoyi.system.utils.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.mybatis.logging.Logger;
import org.mybatis.logging.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponentsBuilder;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class AppPackageServiceImpl implements IAppPackageService {

    private static final Logger logger = LoggerFactory.getLogger(AppPackageServiceImpl.class);


    @Value("${file.storage.package}")
    private String storagePath;

    @Value("${file.storage.image}")
    private String appImage;


    @Resource
    private TbAppVersionMapper appVersionMapper;


    @Resource
    private TbAppCatalogMapper appCatalogMapper;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public PackageInfo processPackageUpload(InputStream inputStream, String originalFilename, String fileHash, double fileSizeMB, String formattedSize) throws IOException {

        // 1. 存储文件
        Path packagePath = storePackageVersionFile(inputStream, originalFilename);
        // 2. 解析包信息
        PackageInfo packageInfo;
        try (InputStream freshStream = Files.newInputStream(packagePath)) {
            packageInfo = HarmonyAppParser.parse(freshStream);
        }
        String packageUrl = generatePackageUrl(packagePath.getFileName().toString());

        // 3. 处理图标文件
        // 3. 处理图标文件并获取存储路径
        String iconUrl = processAppIcon(packageInfo.getIconName(), packageInfo.getIconBytes());
        packageInfo.setIconUrl(iconUrl);

        packageInfo.setPackageUrl(packageUrl);
        packageInfo.setPackageHash(fileHash);
        packageInfo.setFileSizeMB(fileSizeMB);
        packageInfo.setFormattedSize(formattedSize);
        return packageInfo;
    }

    /**
     * 处理应用图标文件并返回访问URL
     */
    private String processAppIcon(String iconName, byte[] iconBytes) throws IOException {
//        if (iconBytes == null || iconBytes.length == 0) {
//            log.warn("Icon bytes is null or empty for icon: {}", iconName);
//            return null;
//        }

        if (iconName == null || iconName.trim().isEmpty()) {
            log.warn("Icon name is null or empty");
            return null;
        }

        // 获取文件名（最后一个斜杠后的内容）
        String fileName = extractFileName(iconName);
        // 构建完整存储路径
        Path storagePath = Paths.get(appImage, fileName);
        // 确保目录存在
        Files.createDirectories(storagePath.getParent());
        // 写入文件
        Files.write(storagePath, iconBytes);

        log.info("App icon saved to: {}", storagePath.toString());
        // 返回图标的访问URL（根据你的实际URL结构生成）
        return appImage + storagePath.getFileName().toString();
    }

    /**
     * 生成图标文件的访问URL
     */
    private String generateIconUrl(String fileName) {
        // 根据你的实际URL结构来生成，这里提供几种常见方式：

        // 方式1：相对URL路径
        // return "/app-images/" + fileName;

        // 方式2：完整的HTTP URL（需要配置域名）
        // return "https://your-domain.com/app-images/" + fileName;

        // 方式3：使用配置文件中的基础URL
        // return appImageBaseUrl + "/" + fileName;

        // 方式4：直接返回文件存储的相对路径（如果前端可以访问）
        return fileName;
    }

    /**
     * 从路径中提取文件名
     */
    private String extractFileName(String iconPath) {
        // 处理Windows和Unix风格的路径分隔符
        int lastSlashIndex = iconPath.lastIndexOf('/');
        int lastBackslashIndex = iconPath.lastIndexOf('\\');
        int lastIndex = Math.max(lastSlashIndex, lastBackslashIndex);

        if (lastIndex >= 0 && lastIndex < iconPath.length() - 1) {
            return iconPath.substring(lastIndex + 1);
        } else {
            // 如果没有分隔符，直接返回原字符串
            return iconPath;
        }
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public AppVersionUploadResult processPackageVersionUpload(InputStream inputStream, String originalFilename, String fileHash, double fileSizeMB, String formattedSize) throws IOException {

        AppVersionUploadResult result = new AppVersionUploadResult();
        // 1. 存储文件
        Path packagePath = storePackageVersionFile(inputStream, originalFilename);
        //获取文件保存目录
        File storageDir = packagePath.getParent().toFile();
        // 2. 生成文件访问URL
        String packageUrl = generatePackageUrl(packagePath.getFileName().toString());
        // 3. 解析包信息
        AppVersionInfo versionInfo;
        try (InputStream freshStream = Files.newInputStream(packagePath)) {
            versionInfo = HarmonyAppVersionParser.parse(freshStream);
        }
        result.setOriginalFilename(originalFilename);
        result.setStoragePath(storageDir.getAbsolutePath());
        // 4. 设置文件信息
        versionInfo.setPackageHash(fileHash);
        versionInfo.setFileSizeMB(fileSizeMB);
        versionInfo.setFormattedSize(formattedSize);
        versionInfo.setPackageUrl(packageUrl);
        //设置全量包信息
        result.setFullPackageInfo(versionInfo);

        // 5. 检查 bsdiff 工具是否可用
//        if (!BsDiffUtil.isBsDiffAvailable()) {
//            result.setPatchPackages(new ArrayList<>());
//            return result;
//        }
        // 6. 获取历史版本并生成差分包信息
        List<PatchPackageInfo> patchPackages = generateDifferentialPackagesInfo(versionInfo, storageDir);
        // 设置差分包信息
        result.setPatchPackages(patchPackages);

        return result;

    }

    private List<PatchPackageInfo> generateDifferentialPackagesInfo(AppVersionInfo newVersionInfo, File storageDir) {
        List<PatchPackageInfo> patchInfos = new ArrayList<>();
        String packageName = newVersionInfo.getPackageName();
        String newVersionCode = newVersionInfo.getVersionCode();
        // 1. 从数据库获取同包名的所有历史版本
        List<TbAppVersion> historyVersions = appVersionMapper.selectLastVersions(packageName);
        if (historyVersions == null || historyVersions.isEmpty()) {
            return patchInfos;
        }
        // 2. 确保新上传的版本文件确实存在于指定的存储目录中，然后才能用它来为历史版本生成差分包。
        File newFile = new File(storageDir, new File(newVersionInfo.getPackageUrl()).getName());
        if (!newFile.exists()) {
            log.warn("新版本文件不存在: {}", newFile.getAbsolutePath());
            return patchInfos;
        }
        for (TbAppVersion oldVersion : historyVersions) {
            try {
                // 跳过相同版本
                String oldVersionCode = oldVersion.getPackageVersioncode();
                if (oldVersionCode.equals(newVersionCode)) {
                    log.info("跳过相同版本: {}", oldVersionCode);
                    continue;
                }
                // 获取历史版本文件
                File oldFile = getHistoryVersionFile(oldVersion, storageDir);
                if (oldFile == null || !oldFile.exists()) {
                    log.warn("历史版本文件不存在: {}", oldFile.getAbsolutePath());
                    continue;
                }
                // 生成差分包信息
                PatchPackageInfo patchInfo = generateSinglePatchInfo(oldVersion, newVersionInfo, oldFile, newFile, storageDir);
                if (patchInfo != null) {
                    log.info("生成差分包信息: {}", patchInfo);
                    patchInfos.add(patchInfo);
                }
            } catch (Exception e) {
            }
        }
        return patchInfos;
    }

    private PatchPackageInfo generateSinglePatchInfo(TbAppVersion oldVersion, AppVersionInfo newVersionInfo, File oldFile, File newFile, File storageDir) throws IOException {
        String oldVersionName = oldVersion.getPackageVersionnumber();
        String oldVersionCode = oldVersion.getPackageVersioncode();
        String newVersionName = newVersionInfo.getVersionName();
        String newVersionCode = newVersionInfo.getVersionCode();

        // 生成差分包文件名
        String patchFileName = generatePatchFileName(oldVersionName, oldVersionCode, newVersionName, newVersionCode);
        File patchFile = new File(storageDir, patchFileName);

        // 检查是否已经存在（避免重复生成）
        if (patchFile.exists()) {
            log.info("差分包已存在，跳过生成: {}", patchFileName);
            // return createPatchPackageInfo(oldVersion, newVersionInfo, patchFile);
            return null;
        }
        try {
            // 使用BsDiffUtil生成差分包
            PatchResult patchResult = BsDiffUtil.generatePatchWithSize(oldFile, newFile, patchFile, 600);
            boolean success = patchResult.isSuccess();
            if (success && patchFile.exists()) {
                // 创建差分包信息对象
                return createPatchPackageInfo(oldVersion, newVersionInfo, patchFile, patchResult);
            } else {
                // 清理失败的文件
                Files.deleteIfExists(patchFile.toPath());
                return null;
            }
        } catch (Exception e) {
            // 清理失败的文件
            try {
                Files.deleteIfExists(patchFile.toPath());
            } catch (IOException ioException) {
            }
            return null;
        }
    }

    private PatchPackageInfo createPatchPackageInfo(TbAppVersion oldVersion, AppVersionInfo newVersionInfo, File patchFile, PatchResult patchResult) throws IOException {
        PatchPackageInfo patchInfo = new PatchPackageInfo();

        String oldVersionCode = oldVersion.getPackageVersioncode();
        String oldVersionName = oldVersion.getPackageVersionnumber();
        String newVersionCode = newVersionInfo.getVersionCode();
        String newVersionName = newVersionInfo.getVersionName();

        patchInfo.setOldVersionCode(oldVersionCode);
        patchInfo.setOldVersionName(oldVersionName);
        patchInfo.setNewVersionCode(newVersionCode);
        patchInfo.setNewVersionName(newVersionName);
        patchInfo.setPatchFileName(patchFile.getName());
        patchInfo.setFileSizeBytes(patchFile.length());
        patchInfo.setFormattedSize(formatFileSize(patchFile.length()));

        // 统一使用MD5计算哈希
        String md5Hash = calculateFileHash(patchFile); // 使用MD5算法
        patchInfo.setPatchHash(md5Hash);
        // 生成包含MD5的URL
        // patchInfo.setPatchUrl(generatePatchUrlWithMd5(patchFile, md5Hash));
        // 生成不包含MD5的URL
        patchInfo.setPatchUrl(generatePatchUrl(patchFile.getName()));
        patchInfo.setPatchVersionCode(PatchVersionUtil.generatePatchVersionCode(oldVersionCode, newVersionCode));
        patchInfo.setPatchVersionName(PatchVersionUtil.generatePatchVersionName(oldVersionName, newVersionName));
        patchInfo.setPackageName(newVersionInfo.getPackageName());
        patchInfo.setAppName(newVersionInfo.getAppName());
        String remark = String.format("差分包：从版本 %s(%s) 升级到版本 %s(%s)", oldVersionName, oldVersionCode, newVersionName, newVersionCode);
        patchInfo.setRemark(remark);
        // 设置创建时间
        patchInfo.setCreateTime(new Date());
        //将数据入库
        TbAppVersion tbAppVersion = new TbAppVersion();
        tbAppVersion.setPackageId(oldVersion.getPackageId());
        tbAppVersion.setPackageName(newVersionInfo.getPackageName());
        tbAppVersion.setPackageVersioncode(patchInfo.getPatchVersionCode());
        tbAppVersion.setPackageVersionnumber(patchInfo.getPatchVersionName());
        tbAppVersion.setPackageHash(patchInfo.getPatchHash());
        tbAppVersion.setPackageType(2);
        tbAppVersion.setPackageSize(bytesToMB(patchInfo.getFileSizeBytes()));
        tbAppVersion.setOsRequirement(String.valueOf(newVersionInfo.getMinApiVersion()));
        tbAppVersion.setSdkRequirement(newVersionInfo.getCompileSdkVersion());
        tbAppVersion.setPackageUrl(patchInfo.getPatchUrl());
        tbAppVersion.setStatus(1);
        tbAppVersion.setPublishDate(new Date());
        tbAppVersion.setCreateBy(SecurityUtils.getUsername());
        tbAppVersion.setCreateTime(new Date());
        tbAppVersion.setRemark(patchInfo.getRemark());
        tbAppVersion.setPackageSize(patchResult.getSizeMB());
        appVersionMapper.insertTbAppVersion(tbAppVersion);
        return patchInfo;
    }

    private double bytesToMB(long bytes) {
        double mb = bytes / (1024.0 * 1024.0);
        // 保留两位小数
        return Math.round(mb * 100.0) / 100.0;
    }

    /**
     * 计算文件的MD5哈希值
     */
    private String calculateFileHash(File file) throws IOException {
        try (InputStream inputStream = Files.newInputStream(file.toPath())) {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            byte[] buffer = new byte[8192];
            int read;
            while ((read = inputStream.read(buffer)) > 0) {
                digest.update(buffer, 0, read);
            }
            byte[] hash = digest.digest();
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                hexString.append(String.format("%02x", b));
            }
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new IOException("MD5 algorithm not available", e);
        }
    }

    /**
     * 生成包含MD5哈希的下载URL
     */
    private String generatePatchUrlWithMd5(File patchFile, String md5Hash) {
        String fileName = patchFile.getName();

        // 方案1：在URL参数中包含MD5
        return String.format(storagePath + "/%s?hash=%s", fileName, md5Hash);

        // 方案2：使用MD5作为文件名的一部分
        // String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
        // String extension = fileName.substring(fileName.lastIndexOf('.'));
        // return String.format("/download/patch/%s_%s%s", baseName, md5Hash.substring(0, 8), extension);

        // 方案3：完全使用MD5作为标识
        // String extension = getFileExtension(fileName);
        // return String.format("/download/patch/%s.%s", md5Hash, extension);
    }


    private File getHistoryVersionFile(TbAppVersion historyVersion, File storageDir) {
        try {
            String packageUrl = historyVersion.getPackageUrl();
            if (packageUrl == null || packageUrl.trim().isEmpty()) {
                return null;
            }

            // 从URL或路径中提取纯文件名
            String fileName = extractFileNameFromUrl(packageUrl);

            // 确保文件一定在storageDir目录下
            File historyFile = new File(storageDir, fileName);

            if (!historyFile.exists()) {
                // 尝试查找可能的不同命名格式
//                File alternativeFile = findAlternativeFileName(storageDir, historyVersion);
//                if (alternativeFile != null) {
//                    return alternativeFile;
//                }
                return null;
            }

            if (!historyFile.isFile()) {
                return null;
            }
            if (historyFile.length() == 0) {
                return null;
            }
            return historyFile;

        } catch (Exception e) {
            return null;
        }
    }


    /**
     * 从URL或路径中提取文件名（支持Windows和Linux路径）
     */
    private String extractFileNameFromUrl(String url) {
        if (url == null || url.trim().isEmpty()) {
            return null;
        }

        // 处理URL格式: http://example.com/uploads/app-v1.0.0.hap
        if (url.contains("://")) {
            return url.substring(url.lastIndexOf("/") + 1);
        }

        // 处理Windows绝对路径: D:\storage\packages\app-v1.0.0.hap
        if (url.contains(":\\")) {
            return url.substring(url.lastIndexOf("\\") + 1);
        }

        // 处理Linux绝对路径: /storage/packages/app-v1.0.0.hap
        if (url.startsWith("/")) {
            return url.substring(url.lastIndexOf("/") + 1);
        }

        // 处理Windows相对路径: packages\app-v1.0.0.hap
        if (url.contains("\\")) {
            return url.substring(url.lastIndexOf("\\") + 1);
        }

        // 处理Linux相对路径: packages/app-v1.0.0.hap
        if (url.contains("/")) {
            return url.substring(url.lastIndexOf("/") + 1);
        }
        // 如果已经是纯文件名: app-v1.0.0.hap
        return url;
    }


    private String generatePatchFileName(String oldVersionName, String oldVersionCode, String newVersionName, String newVersionCode) {
        // 使用应用名称+版本号的方式，更易读
        String oldAppVersion = buildAppVersionIdentifier(oldVersionName, oldVersionCode);
        String newAppVersion = buildAppVersionIdentifier(newVersionName, newVersionCode);

        return String.format("%s_to_%s.patch.hap", sanitizeFileName(oldAppVersion), sanitizeFileName(newAppVersion));
    }

    /**
     * 构建应用版本标识符
     */
    private String buildAppVersionIdentifier(String versionName, String versionCode) {
        if (versionName == null && versionCode == null) {
            return "unknown";
        }

        if (versionName != null && versionCode != null) {
            return versionName + "_" + versionCode;
        }

        return versionName != null ? versionName : versionCode;
    }

    private String sanitizeFileName(String fileName) {
        if (fileName == null) return "unknown";
        // 限制长度，避免文件名过长
        String sanitized = fileName.replaceAll("[^a-zA-Z0-9._-]", "_");
        return sanitized.length() > 50 ? sanitized.substring(0, 50) : sanitized;
    }

    private String generatePatchUrl(String patchFileName) {
        // 生成差分包访问URL，可以根据实际需求调整
        return storagePath + patchFileName;
    }


    private String formatFileSize(long size) {
        if (size <= 0) return "0 B";
        final String[] units = {"B", "KB", "MB", "GB", "TB"};
        int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
        digitGroups = Math.min(digitGroups, units.length - 1);
        return String.format("%.1f %s", size / Math.pow(1024, digitGroups), units[digitGroups]);
    }


    private Path storePackageVersionFile(InputStream inputStream, String originalFilename) throws IOException {
        // 创建存储目录（如果不存在）
        Path uploadDir = Paths.get(storagePath);
        if (!Files.exists(uploadDir)) {
            Files.createDirectories(uploadDir);
        }
        // 消毒文件名
        String safeFilename = sanitizeFilename(originalFilename);
        // 生成唯一文件名
        String uniqueFilename = UUID.randomUUID() + "-" + safeFilename;
        Path filePath = uploadDir.resolve(uniqueFilename);
        // 保存文件
        Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING);
        return filePath;
    }

    private String generatePackageUrl(String storedFilename) {
        return UriComponentsBuilder.newInstance().path(storagePath).path(storedFilename).build().toUriString();
    }

    private String sanitizeFilename(String filename) {
        // 简单消毒：移除路径信息，只保留文件名
        String safeName = filename.replaceAll("^.*[\\\\/]", "");
        // 可选：进一步清理特殊字符
        return safeName.replaceAll("[^a-zA-Z0-9.-]", "_");
    }


    @Override
    public DataPackageInfo processPackageDataUpload(InputStream inputStream, String originalFilename, String fileHash, double fileSizeMB, String formattedSize) throws IOException {
        // 0. 先将输入流内容读取到字节数组
        byte[] fileContent = IOUtils.toByteArray(inputStream);

        // 1. 存储文件（使用字节数组）
        //Path packagePath = storePackageVersionFile(new ByteArrayInputStream(fileContent), originalFilename);
        Path packagePath = storeDataPackageFile(new ByteArrayInputStream(fileContent), originalFilename);

        // 2. 解析包信息（使用字节数组的新流）
        DataPackageInfo packageInfo;
        try (InputStream freshStream = new ByteArrayInputStream(fileContent)) {
            packageInfo = DataPackageParserUtil.parse(freshStream);
        }
        // 2. 生成文件访问URL（根据你的存储方案实现）
        String filePath = packagePath.getFileName().toString();
        packageInfo.setPackageHash(fileHash);
        packageInfo.setFileSize(fileSizeMB);
        packageInfo.setPackageUrl(generatePackageUrl(filePath));
        return packageInfo;

    }


    // 修改存储方法
    private Path storeDataPackageFile(InputStream fileStream, String originalFilename) throws IOException {
        Path targetPath = Paths.get(storagePath, originalFilename);
        Files.copy(fileStream, targetPath, StandardCopyOption.REPLACE_EXISTING);
        return targetPath;
    }
}


